package de.schnitzeljaeger.model.dao.impl;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import de.schnitzeljaeger.model.dao.IGenericDao;

class GenericDBDao<T> implements IGenericDao<T> {
	
	private final SQLiteDatabase db;
	private final DatabaseUtil util;
	private final Class<T> entityClass;
	private final String table;
	
	public GenericDBDao(Class<T> entityClass, DatabaseUtil util) {
		this.entityClass = entityClass;
		this.util = util;
		db = util.getWritableDatabase();
		
		String entityClassName = entityClass.getSimpleName();
		entityClassName = entityClassName.substring(0, 1).toLowerCase() + entityClassName.substring(1);
		table = util.toSqlName(entityClassName);
	}
	
	@Override
	public long create(T entity) {
		ContentValues values = new ContentValues();

		util.entityToValues(entity, values);
        
		long id = db.insert(table, null, values);

		if (id < 1)
			throw new IllegalStateException("Insert failed");
		
		writeIdToEntity(id, entity);
		
		return id;
	}

	@Override
	public <V> void update(Class<V> specificInterface, V entity, long id) {
		update(entity, id, specificInterface);
	}
	
	@Override
	public <V> void update(Class<V> specificInterface, V entity) {
		Long id = readIdFromEntity(entity);
		
		update(entity, id, specificInterface);
	}
	
	private <V> void update(V entity, Long id, Class<V> specificInterface) {
		if (entity.getClass() != entityClass)
			throw new IllegalArgumentException(String.format("given: %s, expected: %s", entity.getClass(), entityClass));
		
		if (specificInterface == null)
			throw new IllegalArgumentException("specificInterface is null");
		
		if (id == null)
			throw new IllegalArgumentException("Cannot update entity without id");
		
		ContentValues values = new ContentValues();
		
		util.entityToValues(entity, specificInterface, values);
		final String str = values.toString(); 
        db.update(table, values, "_id=" + id, null);
	}

	@Override
	public void delete(long id) {
		db.delete(table, "_id=" + id, null);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <V> V find(Class<V> specificInterface, long id) {
		T entity;
		
		try {
			entity = entityClass.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException(String.format("Cannot instantiate entity of %s", entityClass), e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(String.format("Cannot access entity constructor without args of %s", entityClass), e);
		}
		
		Cursor cursor = db.query(table,
				util.retrievePropertyNames(entityClass, specificInterface),
				"_id=" + id, null, null, null, null);
		
		if (cursor == null || !cursor.moveToFirst())
			throw new IllegalArgumentException("Unknown id: " + id);		
		
		writeIdToEntity(id, entity);
		util.valuesToEntity(cursor, entity);
		
		return (V) entity;
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public <V> List<V> list(Class<V> specificInterface) {
		List<V> result = new LinkedList<V>();
		Cursor cursor = db.query(table,
				util.retrievePropertyNames(entityClass, specificInterface),
				null, null, null, null, null);
		
		if (cursor != null && cursor.moveToFirst()) {
			do {
				T entity;
				
				try {
					entity = entityClass.newInstance();
				} catch (InstantiationException e) {
					throw new RuntimeException(String.format("Cannot instantiate entity of %s", entityClass), e);
				} catch (IllegalAccessException e) {
					throw new RuntimeException(String.format("Cannot access entity constructor without args of %s", entityClass), e);
				}
				
				util.valuesToEntity(cursor, entity);
				result.add((V) entity);
			} while(cursor.moveToNext());
		}
		
		return result;
	}
	
	private void writeIdToEntity(long id, Object entity) {
		try {
			Field idFld = entityClass.getDeclaredField("id");
			idFld.setAccessible(true);
			idFld.set(entity, id);
		} catch (SecurityException e1) {
			throw new RuntimeException(String.format("No permission to access field id of %s", entityClass), e1);
		} catch (NoSuchFieldException e2) {
			throw new RuntimeException(String.format("Unknown field name id of ", entityClass), e2);
		} catch (IllegalArgumentException e3) {
			throw new RuntimeException("Wrong property owner given", e3);
		} catch (IllegalAccessException e4) {
			throw new RuntimeException(String.format("Cannot access field id of %s", entityClass), e4);
		}
	}
	
	private long readIdFromEntity(Object entity) {
		try {
			Field idFld = entityClass.getDeclaredField("id");
			idFld.setAccessible(true);
			return (Long) idFld.get(entity);
		} catch (SecurityException e1) {
			throw new RuntimeException(String.format("No permission to access field id of %s", entityClass), e1);
		} catch (NoSuchFieldException e2) {
			throw new RuntimeException(String.format("Unknown field name id of ", entityClass), e2);
		} catch (IllegalArgumentException e3) {
			throw new RuntimeException("Wrong property owner given", e3);
		} catch (IllegalAccessException e4) {
			throw new RuntimeException(String.format("Cannot access field id of %s", entityClass), e4);
		}
	}
}